use std::collections::HashSet;

use common_base::{async_recursion::async_recursion, once_cell::sync::Lazy, prelude::*};
use common_core::{
    data::{Metadata, MetadataType},
    ServiceContextBase,
};

use super::{
    biz_metadata_manager::BizMetadataManager, dct_metadata_manager::DctMetadataManager,
    do_metadata_manager::DoMetadataManager, fct_metadata_manager::FctMetadataManager,
};

pub enum MetadataManager {
    Biz(BizMetadataManager),
    Dct(DctMetadataManager),
    Do(DoMetadataManager),
    Fct(FctMetadataManager),
}

static BASE_TABLES: Lazy<HashSet<&'static str>> = Lazy::new(|| {
    let mut set = HashSet::new();
    set.insert("SYS_COL_DEF");
    set.insert("SYS_OBJECTS");
    set.insert("SYS_OBJECTS_MLANG");
    set.insert("SYS_OBJCOLS");
    set.insert("SYS_OBJCOLS_MLANG");
    set.insert("SYS_INDEXS");
    set.insert("SYS_KEYS");
    set.insert("SYS_DICTS");
    set.insert("SYS_DICTS_MLANG");
    set.insert("SYS_FACTS");
    set.insert("SYS_FACTS_MLANG");
    set.insert("SYS_MODEL");
    set.insert("SYS_MODEL_MLANG");
    set.insert("SYS_MDL_VAL");
    set.insert("SYS_MDL_CTN");
    set.insert("SYS_MDL_CTN_MLANG");
    set.insert("SYS_MDL_INDEX");
    set
});

impl MetadataManager {
    pub fn get_instance(metadata_type: MetadataType) -> Self {
        let mut metadata = match metadata_type {
            MetadataType::Biz => MetadataManager::Biz(BizMetadataManager),
            MetadataType::Dct => MetadataManager::Dct(DctMetadataManager),
            MetadataType::Fct => MetadataManager::Fct(FctMetadataManager),
            MetadataType::Do => MetadataManager::Do(DoMetadataManager),
        };

        metadata.init_metadata_buffer();
        // if (DataBufferManager.getDataBufferManager(MetaDataManager.class) == null) {
        //     DataBufferManager.putDataBufferManager(MetaDataManager.class, dataBufferManger);
        // }

        metadata
    }

    #[async_recursion]
    pub async fn get_metadata(
        &mut self,
        srv_ctx: &mut ServiceContextBase,
        object_id: &str,
    ) -> Result<Option<Metadata>> {
        if object_id.trim().is_empty() {
            return Ok(None);
        }

        // find metadata in context if exists
        let metadata = Self::exists_ref_metadata(srv_ctx, object_id);
        if metadata.is_some() {
            return Ok(metadata);
        }

        // if force_refresh, load from database always
        // TODO: let force_refresh = ParameterManager.getDefault().getSystemParam("METADATA_FORCE_REFRESH"));
        let force_refresh = srv_ctx.get_value_by_param_name("forceRefresh").eq("1");

        // initialize metadata_buffer as global cache
        self.init_metadata_buffer();

        // generate cache key
        let data_key = Self::get_metadata_cache_key(srv_ctx, object_id);
        if force_refresh || !BASE_TABLES.contains(object_id) {
            debug!("read metadata from db");
            let mut metadata = self.get_metadata_model(srv_ctx, object_id).await?;
            if let Some(metadata) = metadata.as_mut() {
                metadata.set_db_name(srv_ctx.get_value_by_env_name("DataBaseName"));
                metadata.set_db_no(srv_ctx.get_value_by_env_name("DBNO"));

                // refresh cache in data_buffer and context
                // databuffer.add_data(data_key, metadata)
                self.add_metadata_to_context(object_id, metadata);
            } else {
                info!("{}'s metadata == none", object_id);
            }

            Ok(metadata)
        } else {
            debug!("get metadata from cache = {}", data_key);
            todo!()
        }
    }

    // we do not support cache metadata in ctx, because databuffer is better cache mechanism for metadata
    pub fn process_reference_metadata(
        srv_ctx: &mut ServiceContextBase,
        object_id: &str,
        metadata: Metadata,
    ) {
        // TODO
    }

    fn exists_ref_metadata(srv_ctx: &ServiceContextBase, object_id: &str) -> Option<Metadata> {
        // TODO srv_ctx.get_object("_META_DATA_REF_MAP_")
        None
    }

    fn init_metadata_buffer(&mut self) {
        // TODO  get_data_buffer or create it
    }

    fn get_metadata_cache_key(srv_ctx: &ServiceContextBase, object_id: &str) -> String {
        "".to_owned()
    }

    async fn get_metadata_model(
        &self,
        srv_ctx: &mut ServiceContextBase,
        object_id: &str,
    ) -> Result<Option<Metadata>> {
        match self {
            MetadataManager::Biz(inner) => inner
                .get_metadata_model(srv_ctx, object_id)
                .await
                .map(|m| m.map(|b_m| Metadata::Biz(b_m))),
            MetadataManager::Dct(inner) => inner
                .get_metadata_model(srv_ctx, object_id)
                .await
                .map(|m| m.map(|d_m| Metadata::Dct(d_m))),
            MetadataManager::Fct(inner) => inner
                .get_metadata_model(srv_ctx, object_id)
                .await
                .map(|m| m.map(|d_m| Metadata::Fct(d_m))),
            MetadataManager::Do(inner) => inner
                .get_metadata_model(srv_ctx, object_id)
                .await
                .map(|m| m.map(|d_m| Metadata::Do(d_m))),
        }
    }

    fn add_metadata_to_context(&mut self, object_id: &str, metadata: &Metadata) {}
}
